package bitmap;

import java.io.*;
import java.util.ArrayList;
import java.util.BitSet;

import diskmgr.*;
import btree.*;
import global.PageId;
import global.RID;
import global.SystemDefs;
import global.ValueClass;
import columnar.*;
import bufmgr.*;
import global.*;
import heap.*;

/** BitMapFile.java
 * This is the main definition of class BitMapFile, which derives from 
 * abstract base class IndexFile.
 * It provides an insert/delete interface.
 */
public class BitMapFile extends IndexFile implements GlobalConst {

	private final static int MAGIC0=1989;

	private final static String lineSep=System.getProperty("line.separator");

	private static FileOutputStream fos;
	private static DataOutputStream trace;

	private BitMapHeaderPage headerPage;
	private  PageId  headerPageId;
	private String  dbname;  
	private BitSet bitSet;
	private BitSet bitSet1 = new BitSet();
	int bitIndex = 0;
	byte[] byteArray;
	byte[] returnByteArray;
	int offset = 0;

	/**
	 * Access method to data member.
	 * @return  Return a BTreeHeaderPage object that is the header page
	 *          of this btree file.
	 */
	public BitMapHeaderPage getHeaderPage() {
		return headerPage;
	}

	public void insert(Boolean value)
	{
		bitSet.set(bitIndex, value.booleanValue());
		bitIndex = bitIndex + 1;
	}

	public boolean delete(int position)
	{
		bitSet.set(position,false);
		return true;
	}

	private PageId get_file_entry(String filename)         
	throws GetFileEntryException
	{
		try {
			return SystemDefs.JavabaseDB.get_file_entry(filename);
		}
		catch (Exception e) {
			e.printStackTrace();
			throw new GetFileEntryException(e,"");
		}
	}
	/*
	private Page pinPage(PageId pageno) 
	throws PinPageException
	{
		try {
			Page page=new Page();
			SystemDefs.JavabaseBM.pinPage(pageno, page, false/*Rdisk);
			return page;
		}
		catch (Exception e) {
			e.printStackTrace();
			throw new PinPageException(e,"");
		}
	}*/

	private void add_file_entry(String fileName, PageId pageno) 
	throws AddFileEntryException
	{
		try {
			SystemDefs.JavabaseDB.add_file_entry(fileName, pageno);
		}
		catch (Exception e) {
			e.printStackTrace();
			throw new AddFileEntryException(e,"");
		}      
	}
	/*
	private void unpinPage(PageId pageno) 
	throws UnpinPageException
	{ 
		try{
			SystemDefs.JavabaseBM.unpinPage(pageno, false /* = not DIRTY );    
		}
		catch (Exception e) {
			e.printStackTrace();
			throw new UnpinPageException(e,"");
		} 
	}*/

	private void freePage(PageId pageno) 
	throws FreePageException
	{
		try{
			SystemDefs.JavabaseBM.freePage(pageno);    
		}
		catch (Exception e) {
			e.printStackTrace();
			throw new FreePageException(e,"");
		} 

	}
	private void delete_file_entry(String filename)
	throws DeleteFileEntryException
	{
		try {
			SystemDefs.JavabaseDB.delete_file_entry( filename );
		}
		catch (Exception e) {
			e.printStackTrace();
			throw new DeleteFileEntryException(e,"");
		} 
	}

	

	/**  BTreeFile class
	 * an index file with given filename should already exist; this opens it.
	 *@param filename the B+ tree file name. Input parameter.
	 *@exception GetFileEntryException  can not ger the file from DB 
	 *@exception PinPageException  failed when pin a page
	 *@exception ConstructPageException   BT page constructor failed
	 * @throws Exception 
	 */
	public BitMapFile(String filename) throws GetFileEntryException,  
	PinPageException, 
	ConstructPageException, Exception        
	{      
		headerPageId=get_file_entry(filename);
		//headerPage= new  BitMapHeaderPage( headerPageId);       
		//dbname = new String(filename);
		if( headerPageId==null) //file not exist
		{
			headerPage= new  BitMapHeaderPage(); 
			headerPageId= headerPage.getPageId();
			add_file_entry(filename, headerPageId);
			headerPage.set_magic0(MAGIC0);
			headerPage.set_rootId(new PageId(INVALID_PAGE));
		}
		else {
			headerPage = new BitMapHeaderPage( headerPageId );  
		}

		bitSet = new BitSet();
		bitIndex = 0;
		dbname=new String(filename);
	}    


	/**
	 *  if index file exists, open it; else create it.
	 *@param filename file name. Input parameter.
	 *@param keytype the type of key. Input parameter.
	 *@param keysize the maximum size of a key. Input parameter.
	 *@param delete_fashion full delete or naive delete. Input parameter.
	 *           It is either DeleteFashion.NAIVE_DELETE or 
	 *           DeleteFashion.FULL_DELETE.
	 *@exception GetFileEntryException  can not get file
	 *@exception ConstructPageException page constructor failed
	 *@exception IOException error from lower layer
	 *@exception AddFileEntryException can not add file into DB */

	public BitMapFile(String filename, ColumnarFile ColumnFile, int ColumnNo, global.ValueClass valueClass )  
	throws GetFileEntryException, 
	ConstructPageException,
	IOException, 
	AddFileEntryException
	{

		// TO do change to check if header exists or not
		headerPageId=get_file_entry(filename);
		if( headerPageId==null) //file not exist
		{
			headerPage= new  BitMapHeaderPage(); 
			headerPageId= headerPage.getPageId();
			add_file_entry(filename, headerPageId);
			headerPage.set_magic0(MAGIC0);
			headerPage.set_rootId(new PageId(INVALID_PAGE));
			
		}
		else {
			headerPage = new BitMapHeaderPage( headerPageId );  
		}

		bitSet = new BitSet();
		bitIndex = 0;
		dbname=new String(filename);

	}

	/** Close the Bitmap file.  Unpin header page.
	 *@exception PageUnpinnedException  error from the lower layer
	 *@exception InvalidFrameNumberException  error from the lower layer
	 *@exception HashEntryNotFoundException  error from the lower layer
	 *@exception ReplacerException  error from the lower layer
	 */
	public void close()
	throws PageUnpinnedException, 
	InvalidFrameNumberException, 
	HashEntryNotFoundException,
	ReplacerException
	{
		if ( headerPage!=null) {
			SystemDefs.JavabaseBM.unpinPage(headerPageId, true);
			headerPage=null;
		}  
	}

	/** Destroy entire B+ tree file.
	 * @throws Exception 
	 */
	public void destroyFile() 
	throws Exception     
	{
		if( headerPage != null) {
			PageId pgId= headerPage.get_rootId();
			if( pgId.pid != INVALID_PAGE) 
				_destroyFile(pgId);
			unpinPage(headerPageId, false);
			freePage(headerPageId);      
			delete_file_entry(dbname);
			headerPage=null;
		}
	}  


	private void  _destroyFile(PageId pageno) 
	throws IOException, 
	IteratorException, 
	PinPageException,
	ConstructPageException, 
	UnpinPageException, 
	FreePageException, Exception
	{

		//BTSortedPage sortedPage;
		BMPage bm = new BMPage();
		if(!(bm.empty()))
		{
			unpinPage(pageno, false);
			freePage(pageno);
		}
	}
	
	public void writeIntoDataPages() throws Exception{
		
		int numberOfPages = 0;
		if (byteArray.length % 1000 == 0) {
			numberOfPages = byteArray.length / 1000;
		} else {
			numberOfPages = (byteArray.length / 1000) + 1;
		}

		byte[] dataPartition;
		int offset_dataPartition = 0;
		int i=0;
		for (i = 0; i < numberOfPages; i++) {
			int bytesToWrite = 0;
			if (numberOfPages - i == 1) {
				bytesToWrite = byteArray.length - (1000 * (numberOfPages - 1));
			} else {
				bytesToWrite = 1000;
			}
			dataPartition= new byte[bytesToWrite];
			System.arraycopy(byteArray, offset_dataPartition, dataPartition, 0, bytesToWrite);
			// Incrementing the offset in the byte array
			offset_dataPartition = offset_dataPartition + bytesToWrite;
			
			// The header page does not contain any data pages
			if (headerPage.get_rootId().pid == INVALID_PAGE) {
				BMPage newBMPage = new BMPage(); 
				PageId newPageId; 
				
				Page apage =  new Page();
				PageId pageId=SystemDefs.JavabaseBM.newPage(apage,1);
				
				newBMPage.init(pageId,apage); 
				newPageId = newBMPage.getCurPage(); 
				
				newBMPage.setNextPage(new PageId(Page.INVALID_PAGE));
				newBMPage.setPrevPage(headerPageId); 
				
				headerPage.set_rootId(newPageId);
				pinPage(newBMPage.curPage);
				newBMPage.writeBMPageArray(dataPartition); // On current Page data[] array write the segmented split data  
				
				if(numberOfPages == i+1){
					newBMPage.NUMBER_OF_BYTES = (byteArray.length)-((i)*1000);
				}else{
					newBMPage.NUMBER_OF_BYTES = (bytesToWrite);
				}
				unpinPage(newPageId, true); /* = DIRTY */
				updateHeader(newPageId);
			} 
			else {
				PageId pageID = headerPage.get_rootId();
				Page pg = pinPage(pageID);
				
				BMPage newBMPage = new BMPage(pg);
				
				
				while (newBMPage.getNextPage().pid != Page.INVALID_PAGE) {
					System.out.println(newBMPage.getNextPage().pid);
					//PageId temp_PageID = new PageId(newBMPage.getNextPage().pid);
					newBMPage = new BMPage(pinPage(newBMPage.getNextPage()));
					//TEST
					unpinPage(pageID,true);
                    pageID = newBMPage.getCurPage();
                    pinPage(pageID);
					//pageID = temp_PageID;
					//pinPage(pageID);
			}
			//System.out.println("Segment No : " + segments);
			Page apage =  new Page();
			PageId pageId = SystemDefs.JavabaseBM.newPage(apage,1);
			
			BMPage bmPage = new BMPage();
			bmPage.init(pageId, apage);
			
			bmPage.setNextPage(new PageId(INVALID_PAGE));
			bmPage.setPrevPage(newBMPage.getCurPage());
			pageId = bmPage.getCurPage();
			pinPage(newBMPage.curPage);
			pinPage(pageId);
			newBMPage.setNextPage(pageId);
			bmPage.writeBMPageArray(dataPartition);
			
			if(numberOfPages == i+1){
				bmPage.NUMBER_OF_BYTES = (byteArray.length)-((i)*1000);
			}else{
				bmPage.NUMBER_OF_BYTES = (bytesToWrite);
			}
			
			unpinPage(pageId, true); /* = DIRTY */
			unpinPage(newBMPage.curPage, true);
			}
		}
	}
		
	/*
	public void writeIntoDataPages() throws Exception{

		try {
			// Checking whether the header pages is empty or not.
			if(headerPage.get_rootId().pid == INVALID_PAGE){
				PageId newPageId;

				Page apage =  new Page();
				PageId pageId=SystemDefs.JavabaseBM.newPage(apage,1);

				BMPage newBMPage = new BMPage();
				newBMPage.init(pageId, apage);

				newPageId = newBMPage.getCurPage();
				headerPage.set_rootId(newPageId);

				if ( trace != null )
				{
					trace.writeBytes("NEWBMPage " + newPageId + lineSep);
					trace.flush();
				}

				newBMPage.setNextPage(new PageId(Page.INVALID_PAGE));
				newBMPage.setPrevPage(new PageId(Page.INVALID_PAGE));

				convertBitSetIntoByteStream();
				//fromByteArray();

				//pinPage(newBMPage.curPage, newBMPage, false/* Rdisk );
				pinPage(newBMPage.curPage);

				int availableSpace = newBMPage.available_space(); 

				if( availableSpace > byteArray.length){
					newBMPage.writeBMPageArray(byteArray);
					//availableSpace = newBMPage.available_space();
					//readFromDataPages(newBMPage);
					return;
				}else{
					_writeIntoDataPages(newBMPage, 0);
					return;
				}				  
			}
			else
			{
				PageId pageID = headerPage.get_rootId();
				Page pg = pinPage(pageID);
				
				BMPage newBMPage = new BMPage(pg);
				while (newBMPage.getNextPage().pid != Page.INVALID_PAGE)
				{					
					newBMPage = new BMPage(pinPage(newBMPage.getNextPage()));
					unpinPage(pageID);
				}
				_writeIntoDataPages(newBMPage, 0);

			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void _writeIntoDataPages(BMPage inputBMPage, int offset) throws Exception{

		int availableSpace;
		int count = 0;
		availableSpace = inputBMPage.available_space();
		boolean dataTransferred = false;

		byte[] tempArray = new byte[availableSpace];

		for(count=0; count <availableSpace; count++){
			if(count + offset < byteArray.length){
				tempArray[count] = byteArray[count + offset];  
			}else{
				dataTransferred = true;	
				break;
			}
		}

		if(dataTransferred){
			byte[] newTempArray = new byte[count-1];
			System.arraycopy(tempArray, 0, newTempArray, 0, count-1);
			inputBMPage.writeBMPageArray(newTempArray);
			return;
		}else{
			inputBMPage.writeBMPageArray(tempArray);
			if (inputBMPage.getNextPage().pid == Page.INVALID_PAGE){
				//BMPage newBMPage = new BMPage();

				Page apage =  new Page();
				PageId pageId=SystemDefs.JavabaseBM.newPage(apage,1);

				BMPage newBMPage = new BMPage();
				newBMPage.init(pageId, apage);

				pinPage(newBMPage.curPage);

				inputBMPage.setNextPage(newBMPage.curPage);
				newBMPage.setPrevPage(inputBMPage.getCurPage());
				newBMPage.setNextPage(new PageId(Page.INVALID_PAGE));
				_writeIntoDataPages(newBMPage, count + offset);
			}  
		}

	}*/

	public void convertBitSetIntoByteStream(){
		
		byteArray = new byte[(bitIndex - 1 + 7)/8];
		int j = -1;
        for (int i=0; i <bitIndex ; i++){
        	 if ( i % 8 == 0 ) j++;
                if(bitSet.get(i)){
                	byteArray[j] |= 1<<(i%8);
                }              
        }
		
		System.out.println("Input bitset");
		for(int i=0; i < bitSet.length(); i++)
		{
			System.out.print("i:" + i + "value:" + bitSet.get(i));
			System.out.println("\n");
		}
		/*
		byteArray = new byte[bitSet.length()/8+1];
		for (int i=0; i<bitSet.length()-1; i++) {
			if (bitSet.get(i)) {
				byteArray[i/8] |= 1<<(i%8);
			}
		}*/
		
		// New method
		/*
		int byteArrSize = (bitSet.size()) / 8;
		byteArray = new byte[byteArrSize];
		byte b = 0;
		int j = 0;
		for (int i = 0; i < bitSet.size(); i++) {
		if (bitSet.get(i)) {
			j = i % 8;
			b = (byte) (b | (1 << j));
		}
		if ((i + 1) % 8 == 0) {
			byteArray[i / 8] = b;
			b = 0;
		}
		}*/
		
		byte temp = byteArray[0];
		int i = 256; //max number * 2
		while( (i >>= 1) > 0) {
		System.out.print(((temp & i) != 0 ? "1" : "0"));
		}
	}


	public BitSet fromByteArray(byte[] bytes) {
		//byte[] bytes = byteArray;
		BitSet bitsNew = new BitSet();
		for (int i=0; i<bytes.length*8; i++) {
			if ((bytes[bytes.length-i/8-1]&(1<<(i%8))) > 0) {
				bitsNew.set(i);
			}
		}
		
		return bitsNew;
	}


	public BMFileScan new_scan()
	throws Exception
	{
		BMFileScan scan = new BMFileScan();
		try{
		if (headerPage.get_rootId().pid==INVALID_PAGE) {
			System.out.println(headerPage.get_rootId());
			return scan;
		}
		}catch(Exception e){
			System.out.println("Invalid page Exception");
		}
		findRunStart();	
		//constructBitSetByteArray();
		System.out.println("Output bitset");
		for(int i=0; i < bitSet1.length(); i++)
		{
			System.out.print("i:" + i + "value:" + bitSet1.get(i));
			System.out.println("\n");
		}
		scan.bitmap = bitSet1;
		return scan;
	}
	
	void findRunStart()
	throws IOException, 
	IteratorException,
	ConstructPageException, 
	PinPageException, 
	UnpinPageException, Exception
	{
		PageId pageno;
		pageno = headerPage.get_rootId();

		if (pageno.pid == INVALID_PAGE)
		{
			System.exit(0);
		}

		Page pg = pinPage(pageno);
		BMPage newBMPage = new BMPage(pg);
		readFromDataPages(newBMPage);
				
		while (newBMPage.getNextPage().pid != Page.INVALID_PAGE)
		{					
			newBMPage = new BMPage(pinPage(newBMPage.getNextPage()));
			unpinPage(pageno, false);
			readFromDataPages(newBMPage);
		}

	}

	public void readFromDataPages(BMPage newPage) throws IOException{
		byte[] readBytes = newPage.getBMpageArray();
		int numOfBytes = 1004 - newPage.available_space();
		byte[] newBytes = new byte[numOfBytes];
		int count = 0;
		System.arraycopy(readBytes, 21, newBytes, 0, numOfBytes);

		BitSet bs = constructBitSetByteArray(newBytes);
		
		for(int i=0; i<bs.length(); i++)
		{
			bitSet1.set(i, bs.get(i));
		}	
	}
	
	
	
	
	public BitSet constructBitSetByteArray(byte[] bytes) {
		BitSet tempBitSet = new BitSet();
		byte tempByte = 0;
		int index = 0;
		tempByte = bytes[0];
		
		
		int m = 256; //max number * 2
		while( (m >>= 1) > 0) {
		System.out.print(((tempByte & m) != 0 ? "1" : "0"));
		}
		
		
		for (int i = 0; i < bytes.length; i++) {
			tempByte = bytes[i];
			for (int j = 0; j<=7; j++) {
				if (((tempByte >> j) & 1) == 1) {
					tempBitSet.set(index++, true);
				} else {
					tempBitSet.set(index++, false);
				}
			}
		}
		return tempBitSet;
	}
	
	 private Page pinPage(PageId pageno) 
	    throws PinPageException
	    {
	      try {
	        Page page=new Page();
	        SystemDefs.JavabaseBM.pinPage(pageno, page, false/*Rdisk*/);
	        return page;
	      }
	      catch (Exception e) {
		e.printStackTrace();
		throw new PinPageException(e,"");
	      }
	    }

	  /**
	   * short cut to access the unpinPage function in bufmgr package.
	   * @see bufmgr.unpinPage
	   */
	  private void unpinPage(PageId pageno, boolean dirty)
	    throws HFBufMgrException {

	    try {
	      SystemDefs.JavabaseBM.unpinPage(pageno, dirty);
	    }
	    catch (Exception e) {
	      throw new HFBufMgrException(e,"Heapfile.java: unpinPage() failed");
	    }

	  } // end of unpinPage

	@Override
	public boolean Delete(KeyClass data, RID rid)
			throws DeleteFashionException, LeafRedistributeException,
			RedistributeException, InsertRecException, KeyNotMatchException,
			UnpinPageException, IndexInsertRecException, FreePageException,
			RecordNotFoundException, PinPageException,
			IndexFullDeleteException, LeafDeleteException, IteratorException,
			ConstructPageException, DeleteRecException, IndexSearchException,
			IOException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void insert(KeyClass data, RID rid) throws KeyTooLongException,
			KeyNotMatchException, LeafInsertRecException,
			IndexInsertRecException, ConstructPageException,
			UnpinPageException, PinPageException, NodeNotMatchException,
			ConvertException, DeleteRecException, IndexSearchException,
			IteratorException, LeafDeleteException, InsertException,
			IOException {
		// TODO Auto-generated method stub
		
	}
	private void  updateHeader(PageId newRoot)
    throws   IOException, 
	     PinPageException,
	     UnpinPageException, Exception
    {
      
      BitMapHeaderPage header;
      PageId old_data;
      
      
      header = new BitMapHeaderPage( pinPage(headerPageId));
      
      old_data = headerPage.get_rootId();
      header.set_rootId( newRoot);
      
      // clock in dirty bit to bm so our dtor needn't have to worry about it
      unpinPage(headerPageId, true /* = DIRTY */ );
      
      
      // ASSERTIONS:
      // - headerPage, headerPageId valid, pinned and marked as dirty
      
    }

	
	
}